package marek.ai;
/**
 * By Marek Jankowski
 * jankowski.marek@gmail.com
 * Any comments or questions are welcome.
 * 
 * License: GNU GPL v3: http://www.gnu.org/licenses/gpl.html
 */


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.DebugGraphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
 * Chart implementation showing the perceptron in action. 
 * Gives good visual reference of the runtime process.
 * Chart supports clicks to classify test data 
 * Uses the Perceptron class
 */
public class PerceptronChart extends JPanel  implements MouseListener {
	private final static int PIXELS_PER_UNIT = 100; 
	private final static int ORIGIN_X = 250;
	private final static int ORIGIN_Y = 350;
	private Perceptron perceptron;
	public Perceptron getPerceptron() {
		return perceptron;
	}

	private Integer testx = null;
	private Integer testy = null;
	
	public PerceptronChart(Perceptron perceptron) {
		super(new BorderLayout());
		this.perceptron = perceptron;
		this.setDebugGraphicsOptions(DebugGraphics.NONE_OPTION);
		this.setPreferredSize(new Dimension(400, 400));
		this.addMouseListener(this);
	}
	
	public void paintComponent(Graphics g) {
		  g.clearRect(0, 0, this.getWidth(), this.getHeight());
		  paintGrid(g);
		  paintTrainingSet(g);
	}
	
	private void paintGrid(Graphics g) {
		g.translate(ORIGIN_X, ORIGIN_Y);
		g.drawLine(getXPos(0), getYPos(0), getXPos(0), getYPos(2.5));
		g.drawLine(getXPos(0), getYPos(0), getXPos(2.5), getYPos(0));
		for (double xy = 0.0; xy <= 2.5; xy+=0.5) {
			g.drawString(xy+"", getXPos(xy), getYPos(0));
			g.drawString(xy+"", getXPos(0), getYPos(xy));
		}
		
	}
	
	private void paintTrainingSet(Graphics g) {
		double[][] posSet = perceptron.getPositiveSet();
		double[][] negSet = perceptron.getNegativeSet();
		g.setColor(Color.GREEN);
		for (int i = 0; i < posSet.length; i++) {
			double[] xSet = posSet[i];
			g.fillOval(getXPos(xSet[0])-5, getYPos(xSet[1])-5, 10, 10);
		}
		g.setColor(Color.RED);
		for (int i = 0; i < negSet.length; i++) {
			double[] xSet = negSet[i];
			g.fillOval(getXPos(xSet[0])-5, getYPos(xSet[1])-5, 10, 10);
		}
		g.setColor(Color.BLUE);
		double wx = round(perceptron.getWeights()[0]);
		double wy = round(perceptron.getWeights()[1]);
		double wThresh = round(perceptron.getThreshold());
		g.drawLine(0, 0, getXPos(wx), getYPos(wy));
		g.drawString("w:" + wx + ", " +  wy, getXPos(wx), getYPos(wy));
		
		// Get dividing hyperlane
		// Calc yaxis angle
		double yaxisAng = Math.atan(wx/wy);
		double complAng = Math.PI/2 - yaxisAng;
		System.out.println("angle = " + Math.toDegrees(yaxisAng));
		System.out.println("complemantary angle = " + Math.toDegrees(complAng));
		
		// Get hyperplane starting point x using complementary angle
		double slopeX = -Math.tan(complAng)*wy;
		System.out.println("slopeX = " + slopeX);

		// calculate slope
		double slope = wy/slopeX;
		System.out.println("slope = " + slope);
		
		// Calculate bias
		double bias = slope/wx*wThresh;
		System.out.println("bias = (slope/wx*wThresh) = " + round(slope) + "/" + round(wx) +"*" + round(wThresh) + " = "+ bias);
		
		// Start point: x = -10.5
		double hx1 = -10.5;
		double hy1 = slope * hx1 + bias;
		
		// Get second hyperplane point
		// End point: x = 10.5
		double hx2 = 10.5;
		double hy2 = slope * hx2 + bias;

		// draw hyperplane to second point
		g.setColor(Color.ORANGE);
		g.drawLine(getXPos(hx1), getYPos(hy1), getXPos(hx2), getYPos(hy2));

		// draw test point if any
		if (perceptron.trained && testx != null && testy != null) {
			System.out.println("test x.y = " + getXCoord(testx) + "," + getYCoord(testy));			
			double testCase[] = {getXCoord(testx), getYCoord(testy), 1};
			Color testcolor = perceptron.evaluate(testCase) > 0 ? Color.GREEN : Color.RED;
			g.setColor(testcolor);
			g.fillOval(getXPos(getXCoord(testx))-5, getYPos(getYCoord(testy))-5, 10, 10);
		}
		
		// Color fill negative and positive zones
		for (double ix = -10.0; ix <= 10.5; ix = round(ix+0.1)) {
			int prevResult = -99;
			for (double iy = -10.0; iy <= 10.5; iy = round(iy+0.1)) {
				double test[] = {ix,iy,1};
				int res = perceptron.evaluate(test);
				if (res > 0) {
					g.setColor(Color.GREEN);
				} else {
					g.setColor(Color.RED);
				}
				g.drawOval(getXPos(ix)-2, getYPos(iy)-2,4,4);
				if (ix == 0 && res != prevResult && prevResult != -99) {
						System.out.println("tested bias = " + iy);
				}
				prevResult = res;
			}
		}

		g.setColor(Color.BLACK);
	}
	
	private double round(double d) {
		return Math.round(d*1000)/1000d;
	}
	
	private int getYPos(double yCoord) {
		return (int)(yCoord * -PIXELS_PER_UNIT);
	}
	
	private int getXPos(double xCoord) {
		return (int)(xCoord * PIXELS_PER_UNIT);
	}
	
	private double getXCoord(int xpos) {
		return (double)(xpos-ORIGIN_X)/(double)PIXELS_PER_UNIT ;
	}
	
	private double getYCoord(int ypos) {
		return (double)(ypos-ORIGIN_Y)/(double)-PIXELS_PER_UNIT;
	}

	private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("Perceptron");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(600, 600));
        
        //Create and set up the content pane.
        PerceptronChart perceptronChart = new PerceptronChart(new Perceptron());
        perceptronChart.setOpaque(true); //content panes must be opaque
        frame.setContentPane(perceptronChart);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
        perceptronChart.proceedTraining();
    }
    
    private void proceedTraining() {
		new Thread(new TrainingThread(this)).start();
	}
    
    private class TrainingThread implements Runnable {
    	private PerceptronChart chart;
    	
    	private TrainingThread(PerceptronChart chart) {
    		this.chart = chart;
    	}
    	
		@Override
		public void run() {
			while(!chart.perceptron.train()) {
				chart.repaint();
				synchronized (this) {
					try {
						this.wait(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			chart.repaint();
		}
    	
    }

	public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

	@Override
	public void mouseClicked(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		testx = new Integer(x);
		testy = new Integer(y);
		repaint();
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}


}
